package stub

import (
	"errors"
	"fmt"
	"gitee.com/CloudGuan/rpc-go-backend/idlrpc/protocol"
	"gitee.com/CloudGuan/rpc-go-backend/idlrpc/stubcall"
	"google.golang.org/protobuf/proto"
	"time"
)

type ClientStub struct {
	serviceuuid uint64
	stubID      uint32 //自己的stubid 被 manager 管理
	srvID       uint32
	cimpl       ProxyStub
}

func NewClientStub(impl ProxyStub) *ClientStub {
	stubptr := &ClientStub{
		serviceuuid: impl.GetSrvUUID(),
		stubID:      0,
		srvID:       0,
		cimpl:       impl,
	}
	return stubptr
}

func (sc *ClientStub) GetSrvID() uint32 {
	return sc.srvID
}

func (sc *ClientStub) GetStubID() uint32 {
	return sc.stubID
}

func (sc *ClientStub) SetSrvID(srvID uint32) {
	sc.srvID = srvID
}

func (sc *ClientStub) GetSrvUUID() uint64 {
	return sc.serviceuuid
}

func (sc *ClientStub) GetSrvName() string {
	return sc.cimpl.GetSrvName()
}

//@title 客户端调用stub 包装类
func (sc *ClientStub) CallMetchod(methodid uint32, call *stubcall.ClientStubCall, message proto.Message) (resp *protocol.ResponsePackage, err error) {
	if sc == nil {
		return
	}

	call.SetMethodId(methodid)

	trans := sc.cimpl.GetTransport()
	if trans == nil {
		panic(fmt.Sprintf("[idlrpc] %s,%u,0 Service proxy trans port is invalid \n", sc.cimpl.GetSrvName(), sc.cimpl.GetSrvUUID()))
	}

	if trans.IsClose() {
		//TODO 修改为自己的错误日志
		err = errors.New("[idlrpc] transpory has been closed")
		call.SetErrorCode(protocol.IDL_SERVICE_ERROR)
		return
	}

	if message == nil {
		err = errors.New("[idlrpc] message protocol serizal error ")
		return
	}

	pkg, err := proto.Marshal(message)
	if err != nil {
		call.SetErrorCode(protocol.IDL_SERVICE_ERROR)
		return
	}

	//FIXME 这里可能有空参数问题需要写代码进行测试 不太确定
	//if pkg == nil || len(pkg) == 0 {
	//	call.SetErrorCode(protocol.IDL_SERVICE_ERROR)
	//	return
	//}

	//包装request
	reqmsg := &protocol.RequestPackage{
		Header: &protocol.RpcCallHeader{
			protocol.RpcMsgHeader{
				Length: uint32(protocol.CallHeadSize + len(pkg)),
				Type:   protocol.REQUEST_MSG,
			},
			sc.serviceuuid,
			sc.srvID,
			call.GetCallID(),
			methodid,
		},
		Buffer: pkg,
	}

	sendpkg, _ := protocol.PackReqMsg(reqmsg)
	call.SetSendData(sendpkg[:])
	trans.Send(sendpkg)

	//如果是oneway的方法 不用检查返回和定时器
	if sc.cimpl.IsOneWay(methodid) {
		return nil, nil
	}

	//TODO 等待超时添加定时器脚本 重试在业务层生成代码去做

	clocker := time.NewTimer(time.Duration(call.GetTimeOut()) * time.Millisecond)
	defer clocker.Stop()
	select {
	case resp = <-call.Done():
		//不知道是否应该进行销毁
		call.SetErrorCode(resp.Header.ErrorCode)
	case <-clocker.C:
		//超时 才会需要重试
		call.SetErrorCode(protocol.IDL_RPC_TIME_OUT)
		resp = protocol.BuildTimeOut(call.GetCallID(), call.GetErrorCode())
	}
	return
}

//@title 断线重连重试函数
func (sc *ClientStub) Retry(call *stubcall.ClientStubCall) (resp *protocol.ResponsePackage, err error) {

	if sc == nil {
		return
	}

	trans := sc.cimpl.GetTransport()
	if trans == nil {
		panic(fmt.Sprintf("[idlrpc] %s,%u,0 Service proxy trans port is invalid while retry \n", sc.cimpl.GetSrvName(), sc.cimpl.GetSrvUUID()))
	}

	if call == nil {
		return
	}

	sendpkg := call.GetSendData()
	if len(sendpkg) <= 0 {
		return
	}

	trans.Send(sendpkg)

	//TODO 等待超时添加定时器脚本 重试在业务层生成代码去做
	//如果是oneway的方法 不用检查返回和定时器
	if sc.cimpl.IsOneWay(call.GetMethodId()) {
		return nil, nil
	}

	clocker := time.NewTimer(time.Duration(call.GetTimeOut()) * time.Millisecond)
	defer clocker.Stop()
	select {
	case resp = <-call.Done():
		//不知道是否应该进行销毁
		call.SetErrorCode(resp.Header.ErrorCode)
	case <-clocker.C:
		//超时返回
		call.SetErrorCode(protocol.IDL_RPC_TIME_OUT)
		resp = protocol.BuildTimeOut(call.GetCallID(), call.GetErrorCode())
	}
	return
}

//@title 方法时候标注了oneway 关键字
func (sc *ClientStub) IsOneway(methodid uint32) bool {
	if sc == nil {
		//TODO add panic error
		return false
	}

	if sc.cimpl == nil {
		//TODO add panic error
		return false
	}

	return sc.cimpl.IsOneWay(methodid)
}
